package com.chujian.gateway.filter;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.chujian.gateway.client.FeignClientHolder;
import com.chujian.framework.auth.Accessor;
import com.chujian.framework.auth.SecurityConst;
import com.chujian.framework.core.tsdata.Result;
import com.chujian.framework.exception.AuthException;
import com.chujian.gateway.properties.TokenVerifyProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author ：chujian
 * @since ：2020-07-29
 * Token 验证过滤器
 */
@Component
@Slf4j
public class TokenVerifyFilter implements GlobalFilter, Ordered {
    @Autowired
    private FeignClientHolder feignClientHolder;
    @Autowired
    private TokenVerifyProperties tokenVerifyProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    // 开放接口匹配
    private static final String OPEN_ANT_PATH = "/*/open/**";
    // 静态资源
    private static final List<String> STATIC_RES_SET = Arrays.asList(
            "/**/*.html", "/**/*.css", "/**/*.js",
            "/**/*.png", "/**/*.jpg", "/**/*.jpeg", "/**/*.gif"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (!tokenVerifyProperties.isEnabled()) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();

        // url不验证 ; 包含open不验证
        if (!isUrlNeedVerify(uri)) {
            return chain.filter(exchange);
        }

        // 请求头与请求参数同时验证
        List<String> tokenHeaders = request.getHeaders().get(SecurityConst.TOKEN_HEADER);
        List<String> tokenParams = request.getQueryParams().get(SecurityConst.TOKEN_REQUEST_PARAM);

        String token = CollUtil.isNotEmpty(tokenHeaders)
                ? tokenHeaders.get(0)
                : (CollUtil.isNotEmpty(tokenParams) ? tokenParams.get(0) : "");

        if (StringUtils.isEmpty(token)) {
            throw new AuthException("非法请求（缺失token）");
        }

        // 验证
        Result<Accessor> result = doVerifyToken(uri, token);
        if (result == null || !result.isSuccess()) {
            String msg = "";
            if (result != null) msg = result.getMsg();
            log.error("token验证不通过：{}, token:{}", msg, token);
            throw new AuthException("非法请求（token验证不通过）");
        }

        Accessor accessor = result.getData();
        // 将用户信息加入到请求头中，向下传递
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
                .headers(h -> h.add(SecurityConst.ACCESSOR_HEADER, Base64.encode(JSON.toJSONString(accessor))))
                .build();
        ServerWebExchange newExchange = exchange.mutate().request(serverHttpRequest).build();
        return chain.filter(newExchange);
    }

    /**
     * 执行验证token
     *
     * @param uri
     * @param token
     * @return
     */
    private Result<Accessor> doVerifyToken(String uri, String token) {
        Future<Result<Accessor>> future = feignClientHolder.verifyToken(uri, token);
        try {
            return future.get();
        } catch (Exception e) {
            log.error("token验证失败", e);
            return null;
        }
    }

    @Override
    public int getOrder() {
        return -1;
    }

    private boolean isUrlNeedVerify(String uri) {
        // open 类型的接口
        if (antPathMatcher.match(OPEN_ANT_PATH, uri)) return false;

        // 配置
        for (String path : tokenVerifyProperties.getIgnoreUrls()) {
            if (antPathMatcher.match(path, uri)) {
                return false;
            }
        }

        // 静态资源
        String match = CollUtil.findOne(STATIC_RES_SET, e -> antPathMatcher.match(e, uri));
        if (null != match) return false;

        return true;
    }

}
